package com.siashan.toolkit.crypt.binary;

import com.siashan.toolkit.crypt.CodecPolicy;
import com.siashan.toolkit.crypt.util.StringUtils;

import java.math.BigInteger;
import java.util.Objects;

/**
 * Base64 算法工具类
 *
 * @author siashan
 * @since 1.0.7
 */
public class Base64 extends BaseNCodec {

    /**
     * BASE32字符的长度为6位
     * 它们是由3个八位字节组成的块形成24位字符串,
     * 转换为4个BASE64字符.
     */
    private static final int BITS_PER_ENCODED_BYTE = 6;
    private static final int BYTES_PER_UNENCODED_BLOCK = 3;
    private static final int BYTES_PER_ENCODED_BLOCK = 4;

    /**
     * 此数组是一个查找表，它将6位正整数索引值转换为RFC 2045表1中指定的“Base64字母表”等效值。
     *
     */
    private static final byte[] STANDARD_ENCODE_TABLE = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };

    /**
     * 这是上面标准_ENCODE_表的副本，但带有+和/
     *
     * 更改为-和u以使编码的Base64结果更加URL安全。
     *
     * 此表仅在Base64的模式设置为URL-SAFE时使用。
     */
    private static final byte[] URL_SAFE_ENCODE_TABLE = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
    };

    /**
     * 此数组是一个查找表，用于转换从“Base64字母表”（如指定）中提取的Unicode字符
     *
     * 在RFC 2045的表1中）转换为其6位正整数等价物。不在Base64中的字符
     *
     * 但在数组范围内的字母表转换为-1。
     *
     * 注：“+”和“-”都解码为62.“/”和“_”都解码为63
     *
     * URL_安全和标准base64(另一方面，编码器需要提前知道要发出什么）。
     *
     */
    private static final byte[] DECODE_TABLE = {
            //   0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - /
            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
            -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
            15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _
            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
            41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51                      // 70-7a p-z
    };

    /**
     * Base64使用6位字段。
     */
    /**
     * 用于提取6位的掩码，编码时使用
     */
    private static final int MASK_6BITS = 0x3f;
    /**
     * 用于提取4位的掩码，用于解码最终尾随字符.
     */
    private static final int MASK_4BITS = 0xf;
    /**
     * 用于提取2位的掩码，用于解码最终尾随字符.
     */
    private static final int MASK_2BITS = 0x3;

    /**
     * Base64 解码
     *
     * @param base64Data    包含Base64数据的字节数组
     * @return 包含解码数据的数组.
     * @since v1.0.7
     */
    public static byte[] decodeBase64(final byte[] base64Data) {
        return new Base64().decode(base64Data);
    }

    /**
     * Base64 解码
     *
     * @param base64String  包含Base64数据的字符串
     * @return 包含解码数据的数组
     * @since 1.0.7
     */
    public static byte[] decodeBase64(final String base64String) {
        return new Base64().decode(base64String);
    }

    // 用于加密的整数编码的实现

    /**
     * 根据加密标准（如W3C的XML签名）解码字节64编码的整数.
     *
     * @param pArray    包含base64字符数据的字节数组
     * @return BigInteger
     * @since 1.0.7
     */
    public static BigInteger decodeInteger(final byte[] pArray) {
        return new BigInteger(1, decodeBase64(pArray));
    }

    /**
     * 使用base64算法对二进制数据进行编码，但不分块输出.
     *
     * @param binaryData    要加密的二进制数据
     * @return 字节[]，在UTF-8表示形式中包含Base64字符。
     */
    public static byte[] encodeBase64(final byte[] binaryData) {
        return encodeBase64(binaryData, false);
    }

    /**
     * 使用base64算法对二进制数据进行编码，可选地将输出分块为76个字符块.
     *
     * @param binaryData
     *            包含要编码的二进制数据的数组。
     * @param isChunked
     *            如果{@code true}，此编码器将base64输出分块为76个字符块
     * @return Base64编码数据。
     * @throws IllegalArgumentException
     *             当输入数组需要大于{@link Integer#MAX_VALUE}的输出数组时引发
     */
    public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) {
        return encodeBase64(binaryData, isChunked, false);
    }

    /**
     * 使用base64算法对二进制数据进行编码，可选地将输出分块为76个字符块.
     *
     * @param binaryData
     *            包含要编码的二进制数据的数组。
     * @param isChunked
     *            如果{@code true}，此编码器将base64输出分块为76个字符块
     * @param urlSafe
     *            如果{@code true}，此编码器将发出-和u，而不是通常的+和/字符。
     *            <b>注意：使用URL安全字母表编码时不添加填充.</b>
     * @return Base64编码数据.
     * @throws IllegalArgumentException
     *             当输入数组需要大于{@link Integer#MAX_VALUE}的输出数组时引发
     */
    public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) {
        return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE);
    }

    /**
     * 使用base64算法对二进制数据进行编码，可选地将输出分块为76个字符块.
     *
     * @param binaryData
     *            包含要编码的二进制数据的数组.
     * @param isChunked
     *            如果{@code true}，此编码器将base64输出分块为76个字符块
     * @param urlSafe
     *            如果{@code true}，此编码器将发出-和u，而不是通常的+和/字符.
     *            <b>注意：使用URL安全字母表编码时不添加填充.</b>
     * @param maxResultSize
     *            要接受的最大结果大小。
     * @return Base64编码数据.
     * @throws IllegalArgumentException
     *             当输入数组需要大于maxResultSize的输出数组时引发
     */
    public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked,
                                      final boolean urlSafe, final int maxResultSize) {
        if (binaryData == null || binaryData.length == 0) {
            return binaryData;
        }

        // Create this so can use the super-class method
        // Also ensures that the same roundings are performed by the ctor and the code
        final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
        final long len = b64.getEncodedLength(binaryData);
        if (len > maxResultSize) {
            throw new IllegalArgumentException("Input array too big, the output array would be bigger (" +
                    len +
                    ") than the specified maximum size of " +
                    maxResultSize);
        }

        return b64.encode(binaryData);
    }

    /**
     * 使用base64算法对二进制数据进行编码，并将编码后的输出分块为76个字符块
     *
     * @param binaryData
     *            要编码的二进制数据
     * @return 在76个字符块中分块的Base64个字符
     */
    public static byte[] encodeBase64Chunked(final byte[] binaryData) {
        return encodeBase64(binaryData, true);
    }

    /**
     * 使用base64算法对二进制数据进行编码，但不分块输出.
     *
     *
     * @param binaryData
     *            要编码的二进制数据
     * @return 包含Base64字符的字符串.
     */
    public static String encodeBase64String(final byte[] binaryData) {
        return StringUtils.newStringUsAscii(encodeBase64(binaryData, false));
    }

    /**
     * 使用base64算法的URL安全变体对二进制数据进行编码，但不分块输出。这个
     * url安全变体发出-和u，而不是+和/字符。
     * <b>注意：未添加任何填充.</b>
     * @param binaryData
     *            要编码的二进制数据
     * @return 字节[]，在UTF-8表示形式中包含Base64字符。
     */
    public static byte[] encodeBase64URLSafe(final byte[] binaryData) {
        return encodeBase64(binaryData, false, true);
    }

    /**
     * 使用base64算法的URL安全变体对二进制数据进行编码，但不分块输出。这个url安全变体发出-和u，而不是+和/字符。
     * <b>注意：未添加任何填充.</b>
     * @param binaryData
     *            要编码的二进制数据
     * @return 包含Base64字符的字符串
     */
    public static String encodeBase64URLSafeString(final byte[] binaryData) {
        return StringUtils.newStringUsAscii(encodeBase64(binaryData, false, true));
    }

    /**
     * 根据加密标准（如W3C的XML签名）编码为字节64编码的整数.
     *
     * @param bigInteger
     *            大整数
     * @return 包含base64字符数据的字节数组
     * @throws NullPointerException
     *             如果传入null
     */
    public static byte[] encodeInteger(final BigInteger bigInteger) {
        Objects.requireNonNull(bigInteger, "bigInteger");
        return encodeBase64(toIntegerBytes(bigInteger), false);
    }



    /**
     * 返回{@code octet}是否在基64字母中.
     *
     * @param octet
     *            要测试的值
     * @return {@code true}如果该值是在基64字母表中定义的，否则{@code false}。
     */
    public static boolean isBase64(final byte octet) {
        return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
    }

    /**
     * 测试给定的字节数组，以查看它是否仅包含Base64字母表中的有效字符。目前
     * 方法将空白视为有效。
     *
     * @param arrayOctet
     *            要测试的字节数组
     * @return {@code true}如果所有字节都是Base64字母表中的有效字符，或者如果字节数组为空；否则为{@code false}
     */
    public static boolean isBase64(final byte[] arrayOctet) {
        for (int i = 0; i < arrayOctet.length; i++) {
            if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     *测试给定字符串，查看它是否仅包含Base64字母表中的有效字符。目前方法将空白视为有效。
     *
     * @param base64
     *            要测试的字符串
     * @return {@code true}如果字符串中的所有字符都是Base64字母表中的有效字符，或者字符串为空；{@code false}，否则为
     */
    public static boolean isBase64(final String base64) {
        return isBase64(StringUtils.getBytesUtf8(base64));
    }

    /**
     * 返回不带符号位的{@code biginger}的字节数组表示形式.
     *
     * @param bigInt
     *            要转换的{@code biginger}
     * @return BigInteger参数的字节数组表示形式
     */
    static byte[] toIntegerBytes(final BigInteger bigInt) {
        int bitlen = bigInt.bitLength();
        // round bitlen
        bitlen = ((bitlen + 7) >> 3) << 3;
        final byte[] bigBytes = bigInt.toByteArray();

        if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
            return bigBytes;
        }
        // set up params for copying everything but sign bit
        int startSrc = 0;
        int len = bigBytes.length;

        // if bigInt is exactly byte-aligned, just skip signbit in copy
        if ((bigInt.bitLength() % 8) == 0) {
            startSrc = 1;
            len--;
        }
        final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
        final byte[] resizedBytes = new byte[bitlen / 8];
        System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
        return resizedBytes;
    }

    /**
     * 要使用的编码表：标准或URL安全。注意：上面的DECODE_表保持静态，因为它可以
     * 要解码标准和URL_安全流，但encodeTable必须是成员变量，以便切换
     * 在这两种模式之间。
     */
    private final byte[] encodeTable;

    // 当前只有一个解码表；保持与Base32代码的一致性
    private final byte[] decodeTable = DECODE_TABLE;

    /**
     *用于编码的行分隔符。解码时不使用。仅当lineLength&gt；0
     */
    private final byte[] lineSeparator;

    /**
     * 便利性变量，帮助我们确定缓冲区何时会用完并需要调整大小。
     * {@code decodeSize=3+lineSeparator.length；}
     */
    private final int decodeSize;

    /**
     * 便利性变量，帮助我们确定缓冲区何时会用完并需要调整大小。
     * {@code encodeSize=4+lineSeparator.length；}
     */
    private final int encodeSize;

    /**
     * 创建用于解码（所有模式）和在URL不安全模式下编码的Base64编解码器。
     * <p>
     * 编码时，行长度为0（无分块），编码表为标准编码表。
     * </p>
     *
     * <p>
     * 解码时，支持所有变体。
     * </p>
     */
    public Base64() {
        this(0);
    }

    /**
     * 创建用于在给定URL安全模式下解码（所有模式）和编码的Base64编解码器.
     * <p>
     * 当编码行长度为76时，行分隔符为CRLF，编码表为标准编码表.
     * </p>
     *
     * <p>
     * 解码时，支持所有变体。
     * </p>
     *
     * @param urlSafe
     *            如果{@code true}，则使用URL安全编码。在大多数情况下，这应该设置为{@code false}。
     */
    public Base64(final boolean urlSafe) {
        this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
    }

    /**
     * 创建用于解码（所有模式）和在URL不安全模式下编码的Base64编解码器.
     * <p>
     * 当在构造函数中给出编码行长度时，行分隔符为CRLF，编码表为标准编码表。
     * </p>
     * <p>
     * 在编码数据中，不是4的倍数的行长度实际上仍然是4的倍数。
     * </p>
     * <p>
     * 解码时，支持所有变体。
     * </p>
     *
     * @param lineLength
     *            每行编码数据最多为给定长度（四舍五入到最接近的整数倍）4).
     *            如果线宽&lt；=0，则输出将不会被划分为行（块）。当解码。
     */
    public Base64(final int lineLength) {
        this(lineLength, CHUNK_SEPARATOR);
    }

    /**
     * 创建用于解码（所有模式）和在URL不安全模式下编码的Base64编解码器。
     * <p>
     * 编码时，构造函数中给出了行长度和行分隔符，编码表为标准编码表.
     * </p>
     * <p>
     * 在编码数据中，不是4的倍数的行长度实际上仍然是4的倍数.
     * </p>
     * <p>
     * 解码时，支持所有变体.
     * </p>
     *
     * @param lineLength
     *            每行编码数据最多为给定长度（四舍五入到最接近的整数倍）4).
     *            如果线宽&lt；=0，则输出将不会被划分为行（块）。当解码。
     * @param lineSeparator
     *            每行编码数据都将以这个字节序列结束。
     * @throws IllegalArgumentException
     *             当提供的lineSeparator包含一些base64字符时引发。
     */
    public Base64(final int lineLength, final byte[] lineSeparator) {
        this(lineLength, lineSeparator, false);
    }

    /**
     * 创建用于解码（所有模式）和在URL不安全模式下编码的Base64编解码器。
     * <p>
     * 编码时，构造函数中给出了行长度和行分隔符，编码表为标准编码表.
     * </p>
     * <p>
     * 在编码数据中，不是4的倍数的行长度实际上仍然是4的倍数。
     * </p>
     * <p>
     * 解码时，支持所有变体。
     * </p>
     *
     * @param lineLength
     *            每行编码数据最多为给定长度（四舍五入到最接近的整数倍）4).
     *            如果线宽&lt；=0，则输出将不会被划分为行（块）。当解码。
     * @param lineSeparator
     *            每行编码数据都将以这个字节序列结束.
     * @param urlSafe
     *            我们不发射“+”和“/”，而是分别发射“-”和“10;”。urlSafe仅应用于编码操作。解码可以无缝地处理这两种模式。
     *            <b>注意：使用URL安全字母表时不添加填充.</b>
     * @throws IllegalArgumentException
     *             当{@code lineSeparator}包含Base64个字符时引发.
     */
    public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) {
        this(lineLength, lineSeparator, urlSafe, DECODING_POLICY_DEFAULT);
    }

    /**
     * 创建用于解码（所有模式）和在URL不安全模式下编码的Base64编解码器.
     * <p>
     * 编码时，构造函数中给出了行长度和行分隔符，编码表为标准编码表.
     * </p>
     * <p>
     * 在编码数据中，不是4的倍数的行长度实际上仍然是4的倍数。
     * </p>
     * <p>
     * 解码时，支持所有变体.
     * </p>
     *
     * @param lineLength
     *            每行编码数据最多为给定长度（四舍五入到最接近的整数倍）4).
     *            如果线宽&lt；=0，则输出将不会被划分为行（块）。当解码。
     * @param lineSeparator
     *            每行编码数据都将以这个字节序列结束。
     * @param urlSafe
     *            我们不发射“+”和“/”，而是分别发射“-”和“10;”。urlSafe仅应用于编码操作。解码可以无缝地处理这两种模式。
     *            <b>注意：使用URL安全字母表时不添加填充.</b>
     * @param decodingPolicy 解码策略.
     * @throws IllegalArgumentException
     *             当{@code lineSeparator}包含Base64个字符时引发.
     */
    public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe, final CodecPolicy decodingPolicy) {
        super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK,
                lineLength,
                lineSeparator == null ? 0 : lineSeparator.length,
                PAD_DEFAULT,
                decodingPolicy);
        // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
        // @see test case Base64Test.testConstructors()
        if (lineSeparator != null) {
            if (containsAlphabetOrPad(lineSeparator)) {
                final String sep = StringUtils.newStringUtf8(lineSeparator);
                throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
            }
            if (lineLength > 0) { // null line-sep forces no chunking rather than throwing IAE
                this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
                this.lineSeparator = new byte[lineSeparator.length];
                System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
            } else {
                this.encodeSize = BYTES_PER_ENCODED_BLOCK;
                this.lineSeparator = null;
            }
        } else {
            this.encodeSize = BYTES_PER_ENCODED_BLOCK;
            this.lineSeparator = null;
        }
        this.decodeSize = this.encodeSize - 1;
        this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
    }

    // Implementation of the Encoder Interface

    /**
     * <p>
     * 从inPos开始，对所有提供的数据进行无效字节解码。应至少调用两次：一次
     * 使用要解码的数据，并在inAvail设置为“-1”时提醒解码器已达到EOF。“-1”
     * 解码时不需要调用，但也不会造成伤害。
     * </p>
     * <p>
     * 忽略所有非base64字符。这是处理分块（例如76个字符）数据的方式，因为CR和LF是
     * 默认忽略，但对其他字节也有影响。此方法订阅中的垃圾，
     * 垃圾处理原理：它不会检查提供的数据的有效性.
     * </p>
     *
     * @param in
     *            要进行base64解码的ascii数据的字节[]数组.
     * @param inPos
     *            开始从中读取数据的位置。
     * @param inAvail
     *            从输入中可用于解码的字节数。
     * @param context
     *            要使用的上下文
     */
    @Override
    void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
        if (context.eof) {
            return;
        }
        if (inAvail < 0) {
            context.eof = true;
        }
        for (int i = 0; i < inAvail; i++) {
            final byte[] buffer = ensureBufferSize(decodeSize, context);
            final byte b = in[inPos++];
            if (b == pad) {
                // We're done.
                context.eof = true;
                break;
            }
            if (b >= 0 && b < DECODE_TABLE.length) {
                final int result = DECODE_TABLE[b];
                if (result >= 0) {
                    context.modulus = (context.modulus + 1) % BYTES_PER_ENCODED_BLOCK;
                    context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
                    if (context.modulus == 0) {
                        buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
                        buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
                        buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
                    }
                }
            }
        }

        // Two forms of EOF as far as base64 decoder is concerned: actual
        // EOF (-1) and first time '=' character is encountered in stream.
        // This approach makes the '=' padding characters completely optional.
        if (context.eof && context.modulus != 0) {
            final byte[] buffer = ensureBufferSize(decodeSize, context);

            // We have some spare bits remaining
            // Output all whole multiples of 8 bits and ignore the rest
            switch (context.modulus) {
//              case 0 : // impossible, as excluded above
                case 1: // 6 bits - either ignore entirely, or raise an exception
                    validateTrailingCharacter();
                    break;
                case 2: // 12 bits = 8 + 4
                    validateCharacter(MASK_4BITS, context);
                    context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
                    break;
                case 3: // 18 bits = 8 + 8 + 2
                    validateCharacter(MASK_2BITS, context);
                    context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
                    break;
                default:
                    throw new IllegalStateException("Impossible modulus " + context.modulus);
            }
        }
    }

    /**
     * <p>
     * 从inPos开始，对所有提供的数据进行无效字节编码。必须至少调用两次：一次使用
     * 要编码的数据，一旦inAvail设置为“-1”以警告编码器已达到EOF，则最后刷新
     * 剩余字节（如果不是3的倍数）。
     * </p>
     * <p><b>注意：使用URL安全字母表编码时不添加填充.</b></p>
     *
     * @param in
     *            要进行base64编码的二进制数据的字节[]数组。
     * @param inPos
     *            开始从中读取数据的位置。
     * @param inAvail
     *            输入中可用于编码的字节数。
     * @param context
     *            要使用的上下文
     */
    @Override
    void encode(final byte[] in, int inPos, final int inAvail, final Context context) {
        if (context.eof) {
            return;
        }
        // inAvail < 0 is how we're informed of EOF in the underlying data we're
        // encoding.
        if (inAvail < 0) {
            context.eof = true;
            if (0 == context.modulus && lineLength == 0) {
                return; // no leftovers to process and not using chunking
            }
            final byte[] buffer = ensureBufferSize(encodeSize, context);
            final int savedPos = context.pos;
            switch (context.modulus) { // 0-2
                case 0: // nothing to do here
                    break;
                case 1: // 8 bits = 6 + 2
                    // top 6 bits:
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS];
                    // remaining 2:
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS];
                    // URL-SAFE skips the padding to further reduce size.
                    if (encodeTable == STANDARD_ENCODE_TABLE) {
                        buffer[context.pos++] = pad;
                        buffer[context.pos++] = pad;
                    }
                    break;

                case 2: // 16 bits = 6 + 6 + 4
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS];
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS];
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
                    // URL-SAFE skips the padding to further reduce size.
                    if (encodeTable == STANDARD_ENCODE_TABLE) {
                        buffer[context.pos++] = pad;
                    }
                    break;
                default:
                    throw new IllegalStateException("Impossible modulus " + context.modulus);
            }
            context.currentLinePos += context.pos - savedPos; // keep track of current line position
            // if currentPos == 0 we are at the start of a line, so don't add CRLF
            if (lineLength > 0 && context.currentLinePos > 0) {
                System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
                context.pos += lineSeparator.length;
            }
        } else {
            for (int i = 0; i < inAvail; i++) {
                final byte[] buffer = ensureBufferSize(encodeSize, context);
                context.modulus = (context.modulus + 1) % BYTES_PER_UNENCODED_BLOCK;
                int b = in[inPos++];
                if (b < 0) {
                    b += 256;
                }
                context.ibitWorkArea = (context.ibitWorkArea << 8) + b; //  BITS_PER_BYTE
                if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS];
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS];
                    buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS];
                    buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS];
                    context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
                    if (lineLength > 0 && lineLength <= context.currentLinePos) {
                        System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
                        context.pos += lineSeparator.length;
                        context.currentLinePos = 0;
                    }
                }
            }
        }
    }

    /**
     * 返回{@code octet}是否在Base64字母表中.
     *
     * @param octet
     *            要测试的值
     * @return {@code true}如果该值是在Base64字母表中定义的{@code false}否则.
     */
    @Override
    protected boolean isInAlphabet(final byte octet) {
        return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
    }

    /**
     * 返回当前的编码模式。如果我们是URL安全的，则为True，否则为false.
     *
     * @return 如果我们处于URL安全模式，则为true，否则为false
     */
    public boolean isUrlSafe() {
        return this.encodeTable == URL_SAFE_ENCODE_TABLE;
    }

    /**
     * 验证是否可以在上下文中解码最后的尾随字符一组可能的基64值。
     *
     * <p>如果提供的掩码中的低位为零，则该字符有效。这
     * 用于测试将被丢弃的位中最后一个尾随的base-64位是否为零.
     *
     * @param emptyBitsMask 应为空的低位掩码
     * @param context 要使用的上下文
     *
     * @throws IllegalArgumentException 如果正在检查的位包含任何非零值
     */
    private void validateCharacter(final int emptyBitsMask, final Context context) {
        if (isStrictDecoding() && (context.ibitWorkArea & emptyBitsMask) != 0) {
            throw new IllegalArgumentException(
                    "Strict decoding: Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible encoding. " +
                            "Expected the discarded bits from the character to be zero.");
        }
    }

    /**
     * 验证解码是否允许不可删除的整个尾随字符
     * 用于一个完整的字节。
     *
     * @throws IllegalArgumentException 如果启用了严格解码
     */
    private void validateTrailingCharacter() {
        if (isStrictDecoding()) {
            throw new IllegalArgumentException(
                    "Strict decoding: Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible encoding. " +
                            "Decoding requires at least two trailing 6-bit characters to create bytes.");
        }
    }

}
